package main

import (
	"bufio"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	pathpkg "path"

	"../pb"
)

const (
	WELL  = int64(0)
	ERROR = int64(1)
)

const (
	DIR              = int64(1)
	FILE_NOTFINISHED = int64(2)
	FILE_FINISHED    = int64(3)
	ALL_FINISHED     = int64(4)
)

type fileService struct {
	basePath string
	bufSize  int64
}

func (fs *fileService) SendAndFeedback(srv pb.FileService_GetFileServer, resp *pb.GetFileResponse) error {
	for i := 0; i <= 5; i++ {
		err := srv.Send(resp)
		if err != nil {
			log.Errorf("srv send,error:%v", err)
			return err
		}
		req, err := srv.Recv()
		if err != nil {
			log.Errorf("srv recv,error:%v", err)
			return err
		}
		if req.Flag == WELL {
			return nil
		}
	}
	return fmt.Errorf("resend too much!!")
}

func (fs *fileService) SendFile(fileName string, srv pb.FileService_GetFileServer) error {
	filePath := pathpkg.Join(fs.basePath, fileName)
	file, err := os.Open(filePath)
	if err != nil {
		log.Errorf("os open,error:%v", err)
		return err
	}
	defer file.Close()
	buf := bufio.NewReader(file)
	data := make([]byte, fs.bufSize)
	for {
		length, err := buf.Read(data)
		if err != nil && err != io.EOF {
			log.Errorf("buf read,error:%v", err)
			return err
		}
		if err == io.EOF {
			resp := &pb.GetFileResponse{
				FileName: fileName,
				Type:     FILE_FINISHED,
			}
			return fs.SendAndFeedback(srv, resp)
		}
		resp := &pb.GetFileResponse{
			FileName: fileName,
			FileData: data[0:length],
			CheckSum: MD5(data[0:length]),
			Type:     FILE_NOTFINISHED,
		}
		err = fs.SendAndFeedback(srv, resp)
		if err != nil {
			log.Errorf("send and feedback,error:%v", err)
			return err
		}
	}
	return nil
}

func (fs *fileService) FilesTransfer(relativePath string, srv pb.FileService_GetFileServer) error {
	path := pathpkg.Join(fs.basePath, relativePath)
	info, err := os.Stat(path)
	if err != nil {
		log.Errorf("os stat,error:%v", err)
		return err
	}
	// file
	if !info.IsDir() {
		return fs.SendFile(relativePath, srv)
	}
	// dir
	resp := &pb.GetFileResponse{
		Type:     DIR,
		FileName: relativePath,
	}
	err = fs.SendAndFeedback(srv, resp)
	if err != nil {
		log.Errorf("send and feedback,error:%v", err)
		return err
	}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		log.Errorf("read dir,error:%v", err)
		return err
	}
	for _, f := range files {
		p := pathpkg.Join(relativePath, f.Name())
		if err := fs.FilesTransfer(p, srv); err != nil {
			log.Errorf("files transfer,error:%v", err)
			return err
		}
	}
	return nil
}

func (fs *fileService) GetFile(srv pb.FileService_GetFileServer) error {
	req, err := srv.Recv()
	if err != nil {
		log.Errorf("server recieve,error:%v", err)
		return err
	}
	err = fs.FilesTransfer(req.Path, srv)
	if err != nil {
		log.Errorf("files transfer,error:%v", err)
		return err
	}
	resp := &pb.GetFileResponse{
		Type: ALL_FINISHED,
	}
	return fs.SendAndFeedback(srv, resp)
}

func MD5(text []byte) []byte {
	md5Hash := md5.New()
	md5Hash.Write(text)
	return md5Hash.Sum(nil)
}
